package com.yyw.gulimall.elasticsearch.service.index.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.yyw.common.constant.common.UrlConstant;
import com.yyw.common.constant.search.product.ProductEnum;
import com.yyw.common.to.productsearch.SpuInfoTo;
import com.yyw.common.utils.R;
import com.yyw.gulimall.elasticsearch.config.RestHighLevelClientConfig;
import com.yyw.gulimall.elasticsearch.feign.ProductFeign;
import com.yyw.gulimall.elasticsearch.service.index.IndexSearchService;
import com.yyw.gulimall.elasticsearch.to.AttrTo;
import com.yyw.gulimall.elasticsearch.to.BrandTo;
import com.yyw.gulimall.elasticsearch.vo.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.stream.Collectors;

/**
 * @Classname IndexSearchServiceImpl
 * @Description 首页商品搜索服务
 * @Date 2020/12/2 16:12
 * @Created yyw
 */
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class IndexSearchServiceImpl implements IndexSearchService {

    private final RestHighLevelClient client;

    private final ProductFeign productFeign;

    @Override
    public IndexSearchResultVo search(IndexSearchParamsVo paramsVo) {
        try {
            // 1. 封装条件参数
            SearchRequest searchRequest = builderParams(paramsVo);
            SearchResponse response = client.search(searchRequest, RestHighLevelClientConfig.COMMON_OPTIONS);
            // 2. 封装查询结果
            return builderResult(response, paramsVo);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 封装查询参数
     *
     * @param paramsVo
     * @return
     */
    private SearchRequest builderParams(IndexSearchParamsVo paramsVo) {
        SearchRequest searchRequest = new SearchRequest(ProductEnum.PRODUCT_SEARCH_NAME.getName());
        SearchSourceBuilder builder = new SearchSourceBuilder();
        // 构建bool多条件查询
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        if (!Objects.equals(null, paramsVo.getKeyword())) {
            // 查询skuTitle
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", paramsVo.getKeyword()));

            // 高亮显示
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<span style='color:red'>");
            highlightBuilder.postTags("</span>");
            builder.highlighter(highlightBuilder);
        }
        if (!CollectionUtils.isEmpty(paramsVo.getBrandId())) {
            // 查询品牌
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", paramsVo.getBrandId()));
        }
        if (!Objects.equals(null, paramsVo.getCatalog3Id())) {
            // 查询三级分类
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", paramsVo.getCatalog3Id()));
        }
        if (!Objects.equals(null, paramsVo.getMinPrice()) && BigDecimal.ZERO.compareTo(paramsVo.getMinPrice()) != 0) {
            // 查询最低价格
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("skuPrice").gte(paramsVo.getMinPrice()));
        }
        if (!Objects.equals(null, paramsVo.getMaxPrice()) && BigDecimal.ZERO.compareTo(paramsVo.getMaxPrice()) != 0) {
            // 查询最高价格
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("skuPrice").lte(paramsVo.getMaxPrice()));
        }
        if (!Objects.equals(null, paramsVo.getHasStock())) {
            // 查询是否有库存
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", paramsVo.getHasStock()));
        }
        if (!CollectionUtils.isEmpty(paramsVo.getAttrsVos())) {
            // 查询属性
            paramsVo.getAttrsVos().forEach(e -> {
                String[] split = e.split("_");
                BoolQueryBuilder bool = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("attrs.attrId", split[0]));
                bool.must(QueryBuilders.termsQuery("attrs.attrValue", split[1]));
                boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs", bool, ScoreMode.None));
            });
        }
        // 聚合查询  聚合attr
        NestedAggregationBuilder nested = AggregationBuilders.nested("attr_aggs", "attrs");
        // 聚合attr的ID
        TermsAggregationBuilder attrIdAggs = AggregationBuilders.terms("attr_id_aggs").field("attrs.attrId").size(100);
        // 聚合AttrName
        TermsAggregationBuilder attrNameAggs = AggregationBuilders.terms("attr_name_aggs").field("attrs.attrName").size(100);
        // 聚合Attr的可选值
        TermsAggregationBuilder attrValAggs = AggregationBuilders.terms("attr_val_aggs").field("attrs.attrValue").size(100);
        attrIdAggs.subAggregation(attrNameAggs);
        attrIdAggs.subAggregation(attrValAggs);
        nested.subAggregation(attrIdAggs);
        // 添加到builder去
        builder.aggregation(nested);

        // 聚合Brand 品牌
        TermsAggregationBuilder brandAggregation = AggregationBuilders.terms("brand_agg").field("brandId").size(100);
        TermsAggregationBuilder brandNameAgg = AggregationBuilders.terms("brand_name_agg").field("brandName").size(100);
        TermsAggregationBuilder brandImgAgg = AggregationBuilders.terms("brand_img_agg").field("brandImg").size(100);
        brandAggregation.subAggregation(brandImgAgg);
        brandAggregation.subAggregation(brandNameAgg);
        builder.aggregation(brandAggregation);

        // 聚合catalog 分类
        TermsAggregationBuilder catalogNameAgg = AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(100);
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg").field("catalogId").size(100)
                .subAggregation(catalogNameAgg);
        builder.aggregation(catalogAgg);

        // 排序
        if (!Objects.equals(null, paramsVo.getSort())) {
            String[] sort = paramsVo.getSort().split("_");
            // 升序
            if ("asc".equalsIgnoreCase(sort[1])) {
                builder.sort(sort[0], SortOrder.ASC);
            }else {
                builder.sort(sort[0], SortOrder.DESC);
            }
        }

        // 页数
        builder.size(paramsVo.getPageSize());
        if (paramsVo.getPageNo() < 0) {
            builder.from(0);
        }else {
            builder.from((paramsVo.getPageNo() - 1) * paramsVo.getPageSize());
        }

        String s = builder.toString();
        log.info("构建的DSL语句:{}", s);

        // 构建
        searchRequest.source(builder.query(boolQueryBuilder));
        return searchRequest;
    }


    /**
     * 封装返回结果
     * @param response  ES返回的结果
     * @param paramsVo  构造返回数据 /skuTitle/page信息
     * @return
     */
    private IndexSearchResultVo builderResult(SearchResponse response, IndexSearchParamsVo paramsVo) {
        // 获取总记录数
        long total = response.getHits().getTotalHits().value;
        if (total <= 0) {
            log.warn("没有查到数据...");
            return null;
        }
        // 返回的最终结果
        IndexSearchResultVo resultVo = new IndexSearchResultVo();
        List<SpuInfoTo> spuInfoToList = new ArrayList<>();
        List<IndexSearchAttrsVo> attrsVos = new ArrayList<>();
        List<IndexSearchBrandsVo> brandsVos = new ArrayList<>();
        List<IndexSearchCatalogsVo> catalogsVos = new ArrayList<>();

        // 遍历his数据
        SearchHit[] hits = response.getHits().getHits();
        for (SearchHit hit : hits) {
            String source = hit.getSourceAsString();
            SpuInfoTo spuInfoTo = JSON.parseObject(source, SpuInfoTo.class);
            // 判断是否有高亮数据
            if (!Objects.equals(null, paramsVo.getKeyword())) {
                String skuTitle = hit.getHighlightFields().get("skuTitle").fragments()[0].toString();
                spuInfoTo.setSkuTitle(skuTitle);
            }
            spuInfoToList.add(spuInfoTo);
        }

        // 遍历聚合数据 attrs数据
        ParsedNested attr_aggs = response.getAggregations().get("attr_aggs");
        ParsedLongTerms attr_id_aggs = attr_aggs.getAggregations().get("attr_id_aggs");
        for (Terms.Bucket bucket : attr_id_aggs.getBuckets()) {
            // 获取属性的ID
            long id = bucket.getKeyAsNumber().longValue();
            // 获取属性的名称
            String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attr_name_aggs")).getBuckets().get(0).getKeyAsString();
            // 获取属性的可能的所有值
            List<? extends Terms.Bucket> attrValAggs = ((ParsedStringTerms) bucket.getAggregations().get("attr_val_aggs")).getBuckets();
            List<String> attrVals = attrValAggs.stream().map(e -> e.getKeyAsString()).collect(Collectors.toList());
            // 封装数据
            IndexSearchAttrsVo attrsVo = IndexSearchAttrsVo.builder().attrId(id).attrName(attrName).attrValues(attrVals).build();
            attrsVos.add(attrsVo);
        }

        // 遍历聚合 brand数据
        Terms brand_agg = response.getAggregations().get("brand_agg");
        brand_agg.getBuckets().forEach(e -> {
            long brandId = e.getKeyAsNumber().longValue();
            // 获取品牌名称
            String brandName = ((Terms) e.getAggregations().get("brand_name_agg")).getBuckets().get(0).getKeyAsString();
            // 获取品牌的img
            String brandImg = ((Terms) e.getAggregations().get("brand_img_agg")).getBuckets().get(0).getKeyAsString();
            IndexSearchBrandsVo brandsVo = IndexSearchBrandsVo.builder().brandId(brandId).brandName(brandName).brandImg(brandImg).build();
            brandsVos.add(brandsVo);
        });

        // 遍历聚合 catalog 数据
        Terms catalogAgg = response.getAggregations().get("catalog_agg");
        catalogAgg.getBuckets().forEach(e -> {
            long catalogId = e.getKeyAsNumber().longValue();
            // 获取分类名称
            String catalogName = ((Terms) e.getAggregations().get("catalog_name_agg")).getBuckets().get(0).getKeyAsString();
            IndexSearchCatalogsVo catalogsVo = IndexSearchCatalogsVo.builder().catalogId(catalogId).catalogName(catalogName).build();
            catalogsVos.add(catalogsVo);
        });

        // 封装数据
        resultVo.setSpuInfoToList(spuInfoToList);
        resultVo.setAttrsVos(attrsVos);
        resultVo.setBrandsVos(brandsVos);
        resultVo.setCatalogsVos(catalogsVos);


        // 分页信息-页码
        resultVo.setPageNo(paramsVo.getPageNo());
        resultVo.setPageSize(paramsVo.getPageSize());
        // 分页信息-总记录数
        resultVo.setTotal((int) total);
        //7、分页信息-总页码-计算
        int totalPages = total%paramsVo.getPageSize() == 0 ?(int) total/paramsVo.getPageSize():((int)total/paramsVo.getPageSize()+1);
        resultVo.setTotalPages(totalPages);

        // 封装面包屑功能  属性
        if (!CollectionUtils.isEmpty(paramsVo.getAttrsVos())) {
            // 传递的属性不为空，设置面包屑属性
            List<NavVo> collect = paramsVo.getAttrsVos().stream().map(e -> {
                String[] val = e.split("_");
                // 标记设置了的属性，在页面剔除
                resultVo.getCheckAttrVos().add(Long.parseLong(val[0]));

                // 封装面包屑
                NavVo navVo = NavVo.builder().value(val[1]).build();
                // 远程调用查询属性信息
                R r = productFeign.attrInfo(Long.parseLong(val[0]));
                if (Objects.equals(r.get("code"), 0)) {
                    AttrTo attr = r.getData("attr", new TypeReference<AttrTo>() {});
                    navVo.setName(attr.getAttrName());
                }else {
                    log.error("调用商品远程接口报错...{}",r.get("msg"));
                    navVo.setName(val[0]);
                }
                String u = getUrlByNav(paramsVo.getQuery_path(), "attrsVos", val[0] + "_" + val[1]);
                navVo.setUlr(u);
                return navVo;
            }).collect(Collectors.toList());
            resultVo.setNavListVo(collect);
        }

        // 封装面包屑功能  品牌
        if (!CollectionUtils.isEmpty(paramsVo.getBrandId())) {
            R r = productFeign.getBrandByIds(paramsVo.getBrandId());
            if (Objects.equals(r.get("code"), 0)) {
                List<BrandTo> brands = r.getData("data", new TypeReference<List<BrandTo>>() {});

                StringJoiner joiner = new StringJoiner(",");
                String url = "";
                for (BrandTo brand : brands) {
                    joiner.add(brand.getName());
                    // 设置url
                    url = getUrlByNav(paramsVo.getQuery_path(), "brandId", brand.getBrandId().toString());
                }
                NavVo vo = NavVo.builder().name("品牌").value(joiner.toString()).ulr(url).build();
                resultVo.getNavListVo().add(vo);
            }else {
                log.error("调用商品远程接口报错...{}",r.get("msg"));
            }
        }

        log.info("查询结果：{}", JSON.toJSONString(resultVo));

        return resultVo;
    }

    /**
     * 设置请求的所有参数
     * 传入属性与值，将该属性与值在请求参数中剔除
     * @param queryPath 请求的所有参数
     * @param key 请求的key
     * @param val 要剔除的属性值
     * @return 新的请求地址
     */
    private String getUrlByNav(String queryPath, String key, String val) {
        // 转义
        String url = "";
        try {
            url = URLDecoder.decode(queryPath,"UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        String finalUrl = url;

        // 设置url，不包含该属性的
        String pix = UrlConstant.SEARCH_URL +  "/list.html";
        String u = "";
        // 判断该属性是否存在
        if (finalUrl.indexOf(key + "=" + val) != -1) {
            // 判断该属性是否在中间
            if (finalUrl.indexOf("&" + key + "=" + val + "&") != -1) {
                u = pix + "?" + finalUrl.replace("&" + key + "=" + val, "");
            }else if (finalUrl.indexOf("&" + key + "=" + val) != -1){
                // 判断是否在最后面
                u = pix + "?" + finalUrl.replace("&" + key + "=" + val, "");
            }else if (finalUrl.indexOf(key + "=" + val + "&") != -1) {
                // 判断是否在最前面并且后面还有参数
                u = pix + finalUrl.replace(key + "=" + val + "&", "?");
            }else if (finalUrl.indexOf(key + "=" + val) != -1) {
                // 判读是否在最前面没有参数了
                u = pix + finalUrl.replace(key + "=" + val, "");
            }
        }
        return u;
    }
}
